package net.zoneland.knowledge.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.page.PageMethod;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import net.zoneland.knowledge.cache.RedisCacheUtil;
import net.zoneland.knowledge.config.BizConfig;
import net.zoneland.knowledge.config.TalkConfig;
import net.zoneland.knowledge.constant.ApproveConstant;
import net.zoneland.knowledge.constant.BizConstant;
import net.zoneland.knowledge.constant.Constants;
import net.zoneland.knowledge.dao.OrgLeaderMapper;
import net.zoneland.knowledge.dao.PartyUserMapper;
import net.zoneland.knowledge.dao.WorkApplyInfoMapper;
import net.zoneland.knowledge.dao.WorkDoneStatusInfoMapper;
import net.zoneland.knowledge.enums.AttachTypeEnum;
import net.zoneland.knowledge.enums.DoneTypeEnum;
import net.zoneland.knowledge.enums.SubmitDraftOperatorType;
import net.zoneland.knowledge.enums.WorkStatusEnum;
import net.zoneland.knowledge.exceptions.WorkApplyException;
import net.zoneland.knowledge.model.*;
import net.zoneland.knowledge.service.*;
import net.zoneland.knowledge.service.bean.ReceiverNameVo;
import net.zoneland.knowledge.service.bean.ReceiverUidVo;
import net.zoneland.knowledge.service.bean.ResultInfo;
import net.zoneland.knowledge.service.bean.SelectUser;
import net.zoneland.knowledge.service.bean.WorkApplyQueryVO;
import net.zoneland.knowledge.utils.OplogUtils;
import net.zoneland.knowledge.utils.WorkApplyOperationUtils;
import net.zoneland.uniflow.client.*;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static net.zoneland.knowledge.service.WorkSubTaskInternalService.APPLY_NOT_EXISTS_ERROR_MSG;
import static net.zoneland.knowledge.service.WorkSubTaskInternalService.GET_LOCK_FAILED;

/**
 * 智办工单业务接口实现.
 *
 * @author zmq
 * @Date 2022/8/9 13:23
 */
@Service
public class WorkApplyInfoServiceImpl implements WorkApplyInfoService {
    /**
     * 日志.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(WorkApplyInfoServiceImpl.class);

    /**
     * 选择人员类型：组织.
     */
    private static final String SELECT_TYPE_ORG = "org";

    /**
     * 流程业务接口.
     */
    @Autowired
    private UniFlowService uniFlowService;
    /**
     * 工单信息数据接口.
     */
    @Autowired
    private WorkApplyInfoMapper workApplyInfoMapper;

    /**
     * 分任务服务接口
     */
    @Autowired
    private WorkSubTaskInternalService workSubTaskInternalService;

    /**
     * 分任务服务接口.
     */
    @Autowired
    private WorkSubTaskHandleService workSubTaskHandleService;

    /**
     * od服务接口.
     */
    @Autowired
    private OaDocsSdkService oaDocsSdkService;

    /**
     * 附件接口.
     */
    @Autowired
    private AttachmentService attachmentService;

    /**
     * redis工具类.
     */
    @Autowired
    private RedisCacheUtil redisCacheUtil;
    /**
     * 党组织管理员
     */
    @Autowired
    private OrgLeaderMapper orgLeaderMapper;
    /**
     * 党员
     */
    @Autowired
    private PartyUserMapper partyUserMapper;

    /**
     * json工具类.
     */
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 已办状态表数据接口
     */
    @Autowired
    private WorkDoneStatusInfoMapper workDoneStatusInfoMapper;

    @Autowired
    private NotifyService notifyService;

    @Autowired
    private EmailService<TaskObject[], WorkApplyInfo> emailService;

    /**
     * 已办状态表服务.
     */
    @Autowired
    private WorkDoneStatusInfoService workDoneStatusInfoService;
    /**
     * 获取全局锁重试次数.
     */
    @Value("${work.lock.retry.count:3}")
    private int retryCount;
    /**
     * 获取全局锁等待时间毫秒.
     */
    @Value("${work.lock.retry.wait.time:1000}")
    private long retryWaitTime;
    /**
     * 智办流程的待办处理人员uid.
     */
    @Value("${work.todo.mock.userId:aihua}")
    private String mockUserId;
    /**
     * 智办流程的待办处理人员姓名.
     */
    @Value("${work.todo.mock.userName:艾华}")
    private String mockUserName;

    @Autowired
    private BizConfig bizConfig;

    @Autowired
    private TalkConfig talkConfig;

    /**
     * 创建工单流程服务线程池
     */
    @Resource(name = "createApplyThreadPoolExecutor")
    private ExecutorService createApplyThreadPoolExecutor;

    /**
     * 保存草稿
     * @param workApplyInfo 工单信息
     * @param user 当前用户
     * @param request 请求信息
     * @param saveType 保存类型（草稿、复制）
     * @throws WorkApplyException 任务工单操作异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveDraft(final WorkApplyInfo workApplyInfo, final LoginUserPojo user, final HttpServletRequest request, final String saveType) throws WorkApplyException {
        try {
            // 复制草稿 初始化任务工单信息
            if(Constants.SAVE_TYPE_COPY.equals(saveType)) {
                copyDraftInitWorkApplyInfo(workApplyInfo);
            }
            // 保存或更新草稿信息
            doSaveDraft(workApplyInfo, user, request, saveType, true);
            // 起草保存也要更新当前工单状态，但不需要加已办记录，这个要特殊处理
            workDoneStatusInfoService.saveDraftWorkApplyDoneStatus(workApplyInfo, user);
        } catch (final NullPointerException e) {
            final String message = String.format("保存工单失败，工单信息[%s]，当前用户[%s]", workApplyInfo, user);
            LOGGER.error(message, e);
            throw new WorkApplyException(message, e);
        }
    }

    /**
     * 复制草稿 初始化任务工单信息
     * @param workApplyInfo 工单数据
     */
    private void copyDraftInitWorkApplyInfo(final WorkApplyInfo workApplyInfo) {
        // 清空指定原工单数据
        workApplyInfo.setId(null);
        workApplyInfo.setPiid(null);
        workApplyInfo.setTiid(null);
        workApplyInfo.setApplyId(null);
        workApplyInfo.setWorkApproveOpinions(null);
        // 初始化指定原工单数据
        workApplyInfo.setTitle(workApplyInfo.getTitle() + "【副本】");
        workApplyInfo.setCreateTime(DateTime.now().toDate());
        workApplyInfo.setStatus(WorkStatusEnum.DRAFT);
        workApplyInfo.setDispatched(false);
    }

    /**
     * 保存或更新草稿信息
     * @param workApplyInfo 工单
     * @param user 当前用户
     * @param request 请求
     * @param saveType 保存类型（保存、提交、复制）
     */
    private void doSaveDraft(final WorkApplyInfo workApplyInfo, final LoginUserPojo user, final HttpServletRequest request, final String saveType, final boolean isCheckFile) {
        // 不是复制的工单，需要初始化本地附件
        if(!Constants.SAVE_TYPE_COPY.equals(saveType) && isCheckFile) {
            Preconditions.checkNotNull(workApplyInfo, "当前工单信息不能为空！");
            workSubTaskInternalService.initWorkApplyInfoAttachments(request, workApplyInfo, user);
            Preconditions.checkNotNull(workApplyInfo.getSubTasks().get(0).getTemplateAttachments(), "草稿环节提交必须上传模板附件！");
        }
        // 为保存草稿操作做的初始化工作
        initWorkApplyForSaveDraft(workApplyInfo, saveType);
        // 没有工单id则插入数据，有则更新
        if (StringUtils.isBlank(workApplyInfo.getId())) {
            workSubTaskInternalService.insertWorkApply(workApplyInfo, user);
        } else {
            workSubTaskInternalService.updateWorkApplyFully(workApplyInfo);
        }
        // 如果是复制的工单，需要上传共享编辑附件
        if(Constants.SAVE_TYPE_COPY.equals(saveType)) {
            uploadSharedEditingAttachment(workApplyInfo);
        }
    }

    /**
     * 为保存草稿操作做的初始化工作。
     */
    private void initWorkApplyForSaveDraft(final WorkApplyInfo workApplyInfo, final String saveType) {
        // 如果保存类型不是提交 则起草保存时当前处理人应该是自己 为了逻辑设置handler
        if (!Constants.SAVE_TYPE_SUBMIT.equals(saveType)) {
            workApplyInfo.setHandlerUid(workApplyInfo.getCreatorUid());
            workApplyInfo.setHandlerName(workApplyInfo.getCreatorName());
        }
        /* 子任务工单集合不为空的话，需要做数据修改 */
        ListUtils.emptyIfNull(workApplyInfo.getSubTasks()).forEach(subTaskInfo -> {
            if(Constants.SAVE_TYPE_COPY.equals(saveType)) { // 如果保存类型是copy，则初始化子任务工单信息
                copyDraftInitWorkSubTaskInfo(subTaskInfo); // 复制草稿 初始化子任务工单信息
            } else if(StringUtils.isBlank(subTaskInfo.getMainTaskId())) { // 如果mainTaskId不存在，则补充
                subTaskInfo.setMainTaskId(subTaskInfo.getId()); // 草稿阶段发起的分任务都是主任务，所以把主任务id设置为自己
            } // 如果保存类型不是copy，并且子任务id存在，则数据不做变化
        });
    }

    /**
     * 复制草稿 初始化子任务工单信息
     * @param subTaskInfo 子任务工单信息对象
     */
    private void copyDraftInitWorkSubTaskInfo(final WorkSubTaskInfo subTaskInfo) {
        // 清空指定原工单数据
        subTaskInfo.setNode(null);
        subTaskInfo.setState(null);
        subTaskInfo.setFinishTime(null);
        subTaskInfo.setMergeAttachments(null);
        subTaskInfo.setReportAttachments(null);
        // 初始化指定原工单数据
        subTaskInfo.setId(OplogUtils.getUuid32());
        subTaskInfo.setMainTaskId(subTaskInfo.getId());
        subTaskInfo.setNewAdd(true);
        // 获取附件byte数据，设置附件id为null，保存会生成新的id，这样保存不对原附件造成影响
        final List<WorkApplyInfoAttachment> templateAttachments = ListUtils.emptyIfNull(subTaskInfo.getTemplateAttachments())
                .stream()
                .map(WorkApplyInfoAttachment::getId)
                .filter(StringUtils::isNotBlank)
                .map(workApplyInfoMapper::findWorkApplyInfoAttachmentsById)
                .peek(this::copyDraftInitWorkApplyInfoAttachment)
                .collect(Collectors.toList());
        subTaskInfo.setTemplateAttachments(templateAttachments);
    }

    /**
     * 复制草稿 初始化子任务工单附件信息
     * @param workApplyInfoAttachment 子任务工单附件信息对象
     */
    private void copyDraftInitWorkApplyInfoAttachment(final WorkApplyInfoAttachment workApplyInfoAttachment) {
        // 清空指定原子任务工单附件数据
        workApplyInfoAttachment.setId(null);
    }

    /**
     * 更新分任务、但不更新分任务的附件。
     *
     * @param list 需要更新的分任务列表
     */
    private void updateSubTasksWithoutAttachment(final List<WorkSubTaskInfo> list) {
        ListUtils.emptyIfNull(list).forEach(workSubTaskInfo -> {
            Preconditions.checkNotNull(workSubTaskInfo.getId(), TASK_ID_BLANK_ERROR_MSG);
            workApplyInfoMapper.updateWorkSubTask(workSubTaskInfo);
        });
    }

    /**
     * 查询工单信息.
     *
     * @param id 工单id
     * @return 工单信息
     */
    @Override
    public WorkApplyInfo findWorkApplyInfoById(String id) throws WorkApplyException {
        return workSubTaskInternalService.findWorkApplyInfoById(id);
    }

    @Override
    public WorkApplyInfo findWorkApplyInfoByIdWithMainApply(final String id) throws WorkApplyException {
        final WorkApplyInfo workApplyInfo = workSubTaskInternalService.findWorkApplyInfoById(id);
        // 所有子工单的主送对象跟主工单主送对象保持一一致, 设置主工单主送对象
        if (!WorkApplyOperationUtils.isMainApply(workApplyInfo)) {
            final String mainWorkApplyId = workApplyInfo.getMainWorkApplyId();
            final WorkApplyInfo mainWorkApplyInfo = workApplyInfoMapper.findWorkApplyInfoById(mainWorkApplyId);
            workApplyInfo.setMainWorkApplyInfo(mainWorkApplyInfo);

            // 查询平级办理人 查询逻辑根据父工单ID查询子工单，排除当前工单
            final String otherHandlerName = workApplyInfoMapper.findWorkApplyInfoByParentIdExcludeIds(workApplyInfo.getParentId(), Sets.newHashSet(id))
                .stream()
                .map(WorkApplyInfo::getAssignUserName)
                .collect(Collectors.joining(Constants.STRING_SEMICOLON));
            workApplyInfo.setOtherHandlerName(otherHandlerName);
        }
        return workApplyInfo;
    }

    /**
     * 通过父工单id查询子工单信息.
     *
     * @param parentId 父工单id
     * @return 工单信息
     */
    @Override
    public List<WorkApplyInfo> findWorkApplyByParentId(final String parentId) {
        return workSubTaskInternalService.findWorkApplyByParentId(parentId);
    }

    /**
     * 任务派发流程主工单提交草稿工单.
     *  @param info    工单信息
     * @param taskId  任务id
     * @param opinion 意见
     * @param user    当前用户
     * @param request
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitDraft(final WorkApplyInfo info, final String taskId,
                            final String opinion, final LoginUserPojo user, final HttpServletRequest request, final String operatorType) {
        Pair<List<TaskObject>, List<WorkApplyInfo>> createSubRes = null;
        // 判断是否需要验证文件模板
        final boolean isCheckFile = !ImmutableSet.of(SubmitDraftOperatorType.REMINDERS_DISPATCH.getName(),
                SubmitDraftOperatorType.REMINDERS_ISSUED.getName()).contains(operatorType) ;
        try {
            info.setOpinion(opinion);
            Preconditions.checkNotNull(info, APPLY_NOT_EXISTS_ERROR_MSG);
            Preconditions.checkNotNull(user, "操作用户不能为空！");
            Preconditions.checkArgument(StringUtils.isBlank(info.getCreatorUid())
                            || StringUtils.equals(user.getUserID(), info.getCreatorUid()), "当前用户不是工单发起人！");
            Preconditions.checkArgument(info.getStatus() == null || info.getStatus() == WorkStatusEnum.DRAFT,
                    "当前工单状态非草稿，不能提交！");
            /*
                流程是否需要管理员审批改成配置：
                之前版本需要部门管理员审批，现在又不需要了
             */
            UserInfo[] nextHandler;
            WorkStatusEnum nextNode = WorkStatusEnum.DEPT_ADMIN_APPROVE;
            String decision = DecisionObject.DECISION_SUBMIT;
            if (bizConfig.isNeedAdminApprove()) {
                nextHandler = getDeptLeader(user);
                Preconditions.checkArgument(ArrayUtils.isNotEmpty(nextHandler), "部门管理员未配置");
                info.setHandlerUid(Stream.of(nextHandler).map(UserInfo::getUserId).collect(Collectors.joining(";")));
                info.setHandlerName(Stream.of(nextHandler).map(UserInfo::getUserName).collect(Collectors.joining(";")));
                info.setStatus(nextNode);
            } else {
                // 如果不需要管理审批，则下一环节就是分派办量环节
                nextNode = WorkStatusEnum.CREATE_SUB_FLOW;
                decision = DecisionObject.DECISION_UPDATE;
                info.setStatus(nextNode);
                info.setDispatched(true);
                info.setHandlerUid(null);
                info.setHandlerName(null);
                nextHandler = WorkApplyOperationUtils.AUTO_NODE_HANDLERS;
            }
            // 设置流程类型，对应Uniflow里的流程模板ID
            info.setFlowType(BizConstant.WISDOM_MAIN_PROCESS_CODE);
            //先保存工单
            this.doSaveDraft(info, user, request, Constants.SAVE_TYPE_SUBMIT, isCheckFile);
            createApplyThreadPoolExecutor.execute(() -> workApplyInfoMapper.insertWorkApproveOpinion(WorkApplyOperationUtils.buildApproveOpinionNew(opinion, user, info, "拟稿", WorkStatusEnum.DRAFT.toString())));
            final TaskObject[] tasks = WorkApplyOperationUtils.queryTask(uniFlowService, user.getUserID(), info, new String[]{WorkStatusEnum.DRAFT.getValue()}, WorkApplyOperationUtils.READY_STATUS);

            // 取第一个任务的tiid
            final String taskIdTmp = ArrayUtils.isNotEmpty(tasks) ? tasks[0].getTiid() : null;
            ResultInfo resultInfo = null;
            if (StringUtils.isNotBlank(taskIdTmp)) {
                // 有可能是退回拟稿后再次提交，这时需要调用完成任务
                resultInfo = uniFlowService.completeTask(taskIdTmp, WorkApplyOperationUtils.convertUserInfo(user),
                                                         decision, "提交",
                        null, nextNode.getValue(), nextHandler,
                        null, null);
            } else {
                resultInfo = uniFlowService.startAndJumpTask(BizConstant.WISDOM_MAIN_PROCESS_CODE,
                        new UserInfo(user.getUserID(), user.getUserName()),
                        WorkApplyOperationUtils.getUniflowApplyId(info),
                        info.getTitle(), nextNode.getValue(),  nextHandler, null);
            }
            Preconditions.checkNotNull(resultInfo, "提交草稿工单异常！");
            Preconditions.checkArgument(resultInfo.isResult(), resultInfo.getInfo());
            // 流程启动成功后，更新业务工单的piid和applyId，便于关联业务信息和流程信息
            final TaskObject[] taskObjects = (TaskObject[]) resultInfo.getData();
            workSubTaskInternalService.updateWorkApplyIdAndPiid(info, taskObjects);
            List<WorkApplyInfo> subApplys = null;

            if (StringUtils.isNotBlank(info.getCopySubmit())) {
                final UserInfo[] userInfos = JSON.parseArray(info.getCopySubmit()).stream().map(item -> {
                    final JSONObject jsonObject = JSON.parseObject(String.valueOf(item));
                    final UserInfo userInfo = new UserInfo();
                    userInfo.setUserId(String.valueOf(jsonObject.get("id")));
                    userInfo.setUserName(String.valueOf(jsonObject.get("label")));
                    return userInfo;
                }).toArray(UserInfo[]::new);

                final UserInfo userInfo = new UserInfo();
                userInfo.setUserId(user.getUserID());
                userInfo.setUserName(user.getUserName());

                // 创建阅办流程
                createApplyThreadPoolExecutor.execute(() -> uniFlowService.createReads(
                        // 取第一个任务的piid
                        taskObjects[0].getPiid(),
                        // 取第一个任务的nodeId
                        taskObjects[0].getNodeId(),
                        userInfos, null, null, null, userInfo, 3));

                // 发送抄送邮件
                final String[] copySubmitUids = Arrays.stream(userInfos).map(UserInfo::getUserId).distinct().toArray(String[]::new);
                emailService.sendEmail(this::sendEmailFunctionByCopySubmit, null, info, Constants.ROLE_COPY_SUBMIT_USER, copySubmitUids);

            }

            if (!bizConfig.isNeedAdminApprove()) {
                //创建分任务的子流程
                createSubRes = workSubTaskInternalService.createSubTask(info, user);
                // 当前工单的分任务的环节和状态被更新了，所以要更新到数据库
                updateSubTasksWithoutAttachment(info.getSubTasks());
                subApplys = createSubRes.getRight();
            }

            //拟稿提交只影响主工单已办信息,没有父工单，传子工单,为了获取子工单待办人，但这一步不用维护子工单已办记录
            workDoneStatusInfoService.dealWorkApplyDoneStatus(info, null, subApplys, user);

            // 如果是321提醒分派，则报结工单并停止流程，否则发待办邮件
            if (StringUtils.equals(operatorType, SubmitDraftOperatorType.REMINDERS_DISPATCH.getName())) {
                // 报结工单
                terminateApply(info.getMainWorkApplyId(), user);
            } else {
                emailService.sendEmail(this::sendEmailFunction, taskObjects, info);
            }

        } catch (final Exception e) { // 内部方法抛出的是Exception
            if (!bizConfig.isNeedAdminApprove()) {
                // 回滚ucc完成的任务
                flowRollbackBack(user, createSubRes);
            }

            throw new WorkApplyException("提交草稿工单失败！", e);
        }
    }

    /**
     * 获取部门党组织对应的管理员名单.
     *
     * @return 部门管理员集合
     */
    private UserInfo[] getDeptLeader(final LoginUserPojo user) {
        //FIXME 此处待调整，通过当前用户的党组织获取对应党组织的管理员
        String orgUUID = partyUserMapper.getOrgUUIDByUserId(user.getUserID());
        List<OrgLeader> leaders = orgLeaderMapper.getLeaderByOrg(orgUUID);
        int size = leaders.size();
        UserInfo[] userInfos = new UserInfo[size];
        for (int i = 0, len = size; i < len; i++) {
            OrgLeader orgLeader = leaders.get(i);
            userInfos[i] = new UserInfo(orgLeader.getUserId(), orgLeader.getUserName());
        }
        return new UserInfo[]{new UserInfo(mockUserId, mockUserName)};
    }

    /**
     * 主工单退回拟稿.
     *
     * @param workApplyId  工单信息
     * @param taskId       任务信息id
     * @param rejectReason 退回原因
     * @param user         操作用户
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rejectTask(String workApplyId, String taskId, String rejectReason, LoginUserPojo user) {
        try {
            Preconditions.checkArgument(StringUtils.isNotBlank(workApplyId), "工单信息id不能为空！");
            Preconditions.checkArgument(StringUtils.isNotBlank(taskId), TASK_ID_BLANK_ERROR_MSG);
            Preconditions.checkArgument(StringUtils.isNotBlank(rejectReason), "退回原因不能为空！");
            Preconditions.checkArgument(WorkApplyOperationUtils
                            .lockWorkApplyInfo(redisCacheUtil, workApplyId, retryCount, 0, retryWaitTime),
                    GET_LOCK_FAILED);
            WorkApplyInfo info = this.findWorkApplyInfoById(workApplyId);
            workSubTaskInternalService.checkDataBeforeApprove(info, WorkStatusEnum.DEPT_ADMIN_APPROVE, user);
            info.setStatus(WorkStatusEnum.DRAFT);
            info.setHandlerUid(info.getCreatorUid());
            info.setHandlerName(info.getCreatorName());
            info.setUpdateTime(DateTime.now().toDate());
            this.workApplyInfoMapper.updateWorkApplyInfo(info);
            this.workApplyInfoMapper.insertWorkApproveOpinion(WorkApplyOperationUtils.buildApproveOpinion(rejectReason, user, info, "退回"));
            ResultInfo res = uniFlowService.completeTask(taskId, new UserInfo(user.getUserID(), user.getUserName()),
                    DecisionObject.DECISION_NO, "退回",
                    rejectReason, WorkStatusEnum.DRAFT.getValue(),
                    new UserInfo[]{new UserInfo(info.getCreatorUid(), info.getCreatorName())},
                    null, null);
            Preconditions.checkArgument(res != null && res.isResult(), "退回工单失败！");
            workDoneStatusInfoService.dealWorkApplyDoneStatus(info, null, null, user);
            emailService.sendEmail(this::sendEmailFunction, (TaskObject[]) res.getData(), info);
        } catch (Exception e) {
            throw new WorkApplyException("退回工单失败!工单id:" + workApplyId + ", taskId:" + taskId, e);
        } finally {
            redisCacheUtil.hdel(workApplyId);
        }
    }

    /**
     * 责任部门领导提交工单，开始创建分任务子流程.
     *
     * @param info    工单信息
     * @param status  状态
     * @param opinion 意见
     * @param user    当前用户
     * @throws WorkApplyException 异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submit(String taskId, WorkApplyInfo info,
                       WorkStatusEnum status, String opinion, LoginUserPojo user) throws WorkApplyException {
        Pair<List<TaskObject>, List<WorkApplyInfo>> createSubRes = null;
        try {
            Preconditions.checkArgument(StringUtils.isNotBlank(taskId), TASK_ID_BLANK_ERROR_MSG);
            workSubTaskInternalService.checkDataBeforeApprove(info, WorkStatusEnum.DEPT_ADMIN_APPROVE, user);
            info.setStatus(WorkStatusEnum.CREATE_SUB_FLOW);
            info.setUpdateTime(DateTime.now().toDate());
            info.setHandlerUid(null);
            info.setHandlerName(null);
            info.setDispatched(CollectionUtils.isNotEmpty(info.getSubTasks()));
            this.workApplyInfoMapper.updateWorkApplyInfo(info);
            WorkApproveOpinion approveOpinion = WorkApplyOperationUtils.buildApproveOpinionNew("", user, info, "同意", WorkStatusEnum.DEPT_ADMIN_APPROVE.toString());
            workApplyInfoMapper.insertWorkApproveOpinion(approveOpinion);
            approveOpinion.setId(UUID.randomUUID().toString());
            approveOpinion.setDecision("分派");
            approveOpinion.setApproveTime(new Date());
            workApplyInfoMapper.insertWorkApproveOpinion(approveOpinion);

            //创建分任务的子流程
            createSubRes = workSubTaskInternalService.createSubTask(info, user);
            // 当前工单的分任务的环节和状态被更新了，所以要更新到数据库
            updateSubTasksWithoutAttachment(info.getSubTasks());
            // 处理主工单
            ResultInfo resultInfo = this.uniFlowService.completeTask(taskId, WorkApplyOperationUtils.convertUserInfo(user), DecisionObject.DECISION_SUBMIT, "提交",
                    null, WorkStatusEnum.DEPT_ADMIN_APPROVE.getValue(),
                    WorkApplyOperationUtils.AUTO_NODE_HANDLERS,
                    info.isDispatched() ? WorkApplyOperationUtils.CREATE_SUB_FLOW_EXTENDS_DATA
                            : WorkApplyOperationUtils.NO_SUB_FLOW_EXTENDS_DATA, null);
            checkUniflowMethodInvokeResult(resultInfo, "责任部门领导提交后创建分任务子流程失败：");
            workDoneStatusInfoService.dealWorkApplyDoneStatus(info, null, createSubRes.getRight(), user);
        } catch (Exception e) {
            // 回滚ucc完成的任务
            flowRollbackBack(user, createSubRes);
            throw new WorkApplyException("责任部门领导提交后创建分任务子流程失败，taskId:" + taskId, e);
        }
    }

    /**
     * 判断ucc接口调用结果.
     *
     * @param resultInfo 调用结果对象
     * @param prefix     错误提示语前缀
     */
    private void checkUniflowMethodInvokeResult(ResultInfo resultInfo, String prefix) {
        Preconditions.checkNotNull(resultInfo, "调用ucc接口返回结果为空！");
        Preconditions.checkArgument(resultInfo.isResult(), prefix + resultInfo.getInfo());
    }

    /**
     * 分任务子流程-组织人员处理上报.
     * <p>
     * 提交到拟稿人确认环境（自动挂起）；然后将父流程提交到责任人审批环节。
     * </p>
     *  @param info    工单信息
     * @param taskId  任务信息
     * @param user    用户
     * @param opinion 操作意见
     * @param request
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void report(final WorkApplyInfo info, final String taskId, final LoginUserPojo user, final String opinion, HttpServletRequest request) {
        // 这个工单下的所有分任务都上报
        workSubTaskHandleService.report(info, info.getSubTasks(), taskId, opinion, user, request);
    }

    /**
     * 责任人确认/子流程拟稿人确认退回.
     *
     * @param info    工单信息
     * @param taskId  任务id
     * @param opinion 意见
     * @param user    操作用户
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rejectOrgHandler(final WorkApplyInfo info, final String taskId,
                                 final String opinion, final LoginUserPojo user) {
        try {
            doRejectOrgHandler(info, taskId, opinion, user, null);
        } catch (WorkApplyException e) {
            throw new WorkApplyException("责任人确认/子流程拟稿人确认退回工单失败!taskId:" + taskId, e);
        }
    }

    /**
     * 执行责任人确认/子流程拟稿人确认退回操作.
     *
     * @param info       工单信息
     * @param taskId     任务id
     * @param opinion    意见
     * @param user       当前用户
     * @param subApplyId 子工单id
     */
    private void doRejectOrgHandler(final WorkApplyInfo info, final String taskId,
                                    final String opinion, final LoginUserPojo user,
                                    final String subApplyId) {
        List<TaskObject> tasks = null;
        try {
            Preconditions.checkNotNull(info, APPLY_NOT_EXISTS_ERROR_MSG);
            Preconditions.checkArgument(StringUtils.isNotBlank(taskId), TASK_ID_BLANK_ERROR_MSG);
            Preconditions.checkArgument(StringUtils.isNotBlank(opinion), "退回原因不能为空！");
            Preconditions.checkArgument(info.getStatus() == WorkStatusEnum.RESPONDER_APPROVE
                            || info.getStatus() == WorkStatusEnum.SUB_ORG_USER_CONFIRM,
                    "当前工单状态不能进行此操作，id->" + info.getId());
            List<WorkApplyInfo> subApplyList = this.workApplyInfoMapper.findWorkApplyInfoByParentApplyId(info.getId());
            Preconditions.checkArgument(CollectionUtils.isNotEmpty(subApplyList),
                    "当前工单的子工单为空！");
            List<WorkApplyInfo> needFinishSubApplies = subApplyList.stream().filter(it -> it.getStatus() == WorkStatusEnum.SUB_CREATOR_CONFIRM
                    || it.getStatus() == WorkStatusEnum.END).collect(Collectors.toList());
            long count = needFinishSubApplies.size();
            Preconditions.checkArgument(count > 0, "当前工单不存在拟稿人确认环节的子工单！");
            boolean rejectSimple = StringUtils.isNotBlank(subApplyId);
            WorkApplyInfo subApply = getSubWorkApplyInfo(subApplyId, rejectSimple);
            Preconditions.checkArgument(WorkApplyOperationUtils
                            .lockWorkApplyInfo(redisCacheUtil, info.getId(), retryCount, 0, retryWaitTime),
                    GET_LOCK_FAILED);
            final boolean allFinish = !rejectSimple || count == 1;
            boolean isMainFlow = StringUtils.isBlank(info.getParentId());
            WorkStatusEnum curState = isMainFlow ? WorkStatusEnum.RESPONDER_APPROVE
                    : WorkStatusEnum.SUB_ORG_USER_CONFIRM;
            workSubTaskInternalService.checkDataBeforeApprove(info, curState, user);
            // 该处需要判断是否有发起过子流程
            WorkStatusEnum targetState = getTartgetState(info, allFinish, isMainFlow);
            UserInfo[] subTaskHandlers = getSubTaskHandlers(info, allFinish, isMainFlow);
            if (allFinish) {
                String handlerUid = Optional.ofNullable(subTaskHandlers)
                        .map(it -> Stream.of(it).map(UserInfo::getUserId).collect(Collectors.joining(";")))
                        .orElse(null);
                String handlerName = Optional.ofNullable(subTaskHandlers)
                        .map(it -> Stream.of(it).map(UserInfo::getUserName).collect(Collectors.joining(";")))
                        .orElse(null);
                info.setHandlerUid(handlerUid);
                info.setHandlerName(handlerName);
            }
            info.setStatus(targetState);
            info.setUpdateTime(DateTime.now().toDate());
            this.workApplyInfoMapper.updateWorkApplyInfo(info);
            updateResultAttachment(info);
            this.workApplyInfoMapper.insertWorkApproveOpinion(WorkApplyOperationUtils
                    .buildApproveOpinion(opinion, user, info, "退回"));
            tasks = rejectSubWorkApplies(opinion, user, info, needFinishSubApplies, rejectSimple, subApply);
            if (allFinish) {
                // 全部结束才改变当前任务状态
                ResultInfo res = uniFlowService.completeTask(taskId, new UserInfo(user.getUserID(), user.getUserName()),
                        DecisionObject.DECISION_NO, "退回",
                        opinion, info.getStatus().getValue(),
                        subTaskHandlers,
                        info.isDispatched() ? WorkApplyOperationUtils.CREATE_SUB_FLOW_EXTENDS_DATA
                                : WorkApplyOperationUtils.NO_SUB_FLOW_EXTENDS_DATA, null);
                Preconditions.checkArgument(res != null && res.isResult(),
                        "责任人确认/子流程拟稿人确认退回工单失败：" + (res != null ? res.getInfo() : "返回为空！"));
                emailService.sendEmail(this::sendEmailFunction, (TaskObject[]) res.getData(), info);
            }
        } catch (WorkApplyException e) {
            WorkApplyOperationUtils.rollbackTasks(uniFlowService, tasks);
            throw new WorkApplyException("责任人确认/子流程拟稿人确认退回工单失败!taskId:" + taskId, e);
        } finally {
            if (info != null) {
                redisCacheUtil.hdel(info.getId());
            }
        }
    }

    /**
     * 获取子任务的处理人集合.
     *
     * @param info       工单信息
     * @param allFinish  是否全部完成
     * @param isMainFlow 是否主流程
     * @return 处理人集合
     */
    private UserInfo[] getSubTaskHandlers(final WorkApplyInfo info, final boolean allFinish,
                                          final boolean isMainFlow) {
        UserInfo[] subTaskHandlers = null;
        if (allFinish) {
            if (isMainFlow) {
                subTaskHandlers = WorkApplyOperationUtils.AUTO_NODE_HANDLERS;
            } else {
                subTaskHandlers = WorkApplyOperationUtils.getSubApplyHandlers(info);
            }
        }
        return subTaskHandlers;
    }

    /**
     * 获取拟稿人确认环节退回操作目标状态.
     *
     * @param info       工单
     * @param allFinish  是否全部完成
     * @param isMainFlow 是否主流程
     * @return 目标状态
     */
    private WorkStatusEnum getTartgetState(final WorkApplyInfo info, final boolean allFinish, final boolean isMainFlow) {
        WorkStatusEnum targetState = info.getStatus();
        if (allFinish) {
            if (isMainFlow) {
                targetState = WorkStatusEnum.CREATE_SUB_FLOW;
            } else {
                targetState = info.isDispatched() ? WorkStatusEnum.SUB_CREATE_SUB_FLOW
                        : WorkStatusEnum.SUB_ORG_USER_HANDLE;
            }
        }
        return targetState;
    }

    /**
     * 退回子流程的工单.
     *
     * @param opinion          意见
     * @param user             用户
     * @param info             当前工单
     * @param rejectSubApplies 待退回的子工单
     * @param rejectSimple     是否单个退回
     * @param subApply         单个退回的子工单
     * @return 执行操作后的ucc任务集合
     */
    private List<TaskObject> rejectSubWorkApplies(final String opinion, final LoginUserPojo user,
                                                  final WorkApplyInfo info, final List<WorkApplyInfo> rejectSubApplies,
                                                  final boolean rejectSimple, final WorkApplyInfo subApply) {
        if (!info.isDispatched()) {
            return Lists.newArrayList();
        }
        List<WorkApplyInfo> needRejectApplies = Lists.newArrayList();
        if (rejectSimple) {
            needRejectApplies.add(subApply);
        } else {
            CollectionUtils.addAll(needRejectApplies, rejectSubApplies.iterator());
        }
        // 将子流程的任务退回到拟稿人确认前的一个环节
        return WorkApplyOperationUtils.rejectSubTasksToPreConfirmNode(workApplyInfoMapper, uniFlowService, needRejectApplies, opinion, user);
    }

    /**
     * 获取子工单信息.
     *
     * @param subApplyId   子工单id
     * @param rejectSimple 是否单个自工单处理
     * @return 子工单信息
     */
    private WorkApplyInfo getSubWorkApplyInfo(final String subApplyId, final boolean rejectSimple) {
        WorkApplyInfo subApply = null;
        if (rejectSimple) {
            subApply = this.workApplyInfoMapper.findWorkApplyInfoById(subApplyId);
            Preconditions.checkNotNull(subApply, "子工单id对应的工单信息不存在！");
            Preconditions.checkArgument(subApply.getStatus() == WorkStatusEnum.SUB_CREATOR_CONFIRM
                            || subApply.getStatus() == WorkStatusEnum.END,
                    "子工单对应的状态非拟稿人确认或结束环节！");
        }
        return subApply;
    }

    /**
     * 责任人确认/子流程拟稿人确认单个退回.
     *
     * @param info       工单信息
     * @param taskId     任务id
     * @param subApplyId 子工单id
     * @param opinion    意见
     * @param user       操作用户
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rejectSimpleOrgHandler(WorkApplyInfo info, String taskId, String subApplyId, String opinion, LoginUserPojo user) {
        List<TaskObject> tasks = null;
        try {
            Preconditions.checkArgument(StringUtils.isNotBlank(subApplyId), "子工单id不能为空！");
            doRejectOrgHandler(info, taskId, opinion, user, subApplyId);
        } catch (WorkApplyException e) {
            WorkApplyOperationUtils.rollbackTasks(uniFlowService, tasks);
            throw new WorkApplyException("责任人确认/子流程拟稿人单个子工单确认退回工单失败!taskId:" + taskId, e);
        } finally {
            if (info != null) {
                redisCacheUtil.hdel(info.getId());
            }
        }
    }

    /**
     * 分任务子流程-组织人员处理分派.
     *
     * @param info    工单信息
     * @param taskId  任务id
     * @param user    用户
     * @param opinion 操作意见
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void dispatch(final WorkApplyInfo info, final String taskId,
                         final LoginUserPojo user, final String opinion) {
        Pair<List<TaskObject>, List<WorkApplyInfo>> createSubRes = null;
        try {
            Preconditions.checkArgument(StringUtils.isNotBlank(taskId), TASK_ID_BLANK_ERROR_MSG);
            workSubTaskInternalService.checkDataBeforeApprove(info, WorkStatusEnum.SUB_ORG_USER_HANDLE, user);

            // 先把当前工单设置到“子流程-分任务办理子流程”环节，没有处理人，并更新到数据库
            info.setStatus(WorkStatusEnum.SUB_CREATE_SUB_FLOW);
            info.setUpdateTime(DateTime.now().toDate());
            info.setDispatched(true);
            info.setHandlerUid(null);
            info.setHandlerName(null);
            this.workApplyInfoMapper.updateWorkApplyInfo(info);

            // 把分任务对象更新到数据库
            for (WorkSubTaskInfo subTask : info.getSubTasks()) {
                if (StringUtils.isNotBlank(subTask.getId())) {
                    this.workApplyInfoMapper.updateWorkSubTask(subTask);
                } else {
                    subTask.setId(UUID.randomUUID().toString());
                    subTask.setWorkApplyId(info.getId());
                    this.workApplyInfoMapper.insertSubTask(subTask);
                }
            }
            //更新结果文档
            updateResultAttachment(info);
            workApplyInfoMapper.insertWorkApproveOpinion(WorkApplyOperationUtils.buildApproveOpinionNew(opinion, user, info, "分派",info.getStatus().toString()));

            // 插入抄送信息
            workSubTaskInternalService.addCopySubmitInfo(info);

            if (StringUtils.isNotBlank(info.getCopySubmit())) {

                TaskObject[] taskObjects = {(TaskObject) this.uniFlowService.getTask(taskId).getData()};

                final UserInfo[] userInfos = JSON.parseArray(info.getCopySubmit()).stream().map(item -> {
                    final JSONObject jsonObject = JSON.parseObject(String.valueOf(item));
                    final UserInfo userInfo = new UserInfo();
                    userInfo.setUserId(String.valueOf(jsonObject.get("id")));
                    userInfo.setUserName(String.valueOf(jsonObject.get("label")));
                    return userInfo;
                }).toArray(UserInfo[]::new);

                // 发送抄送邮件
                final String[] copySubmitUids = Arrays.stream(userInfos).map(UserInfo::getUserId).distinct().toArray(String[]::new);
                emailService.sendEmail(this::sendEmailFunctionByCopySubmit, null, info, Constants.ROLE_COPY_SUBMIT_USER, copySubmitUids);

            }

            //创建分任务的子流程，包括创建子工单、及其分任务，保存到DB，并发起流程
            createSubRes = workSubTaskInternalService.createSubTask(info, user);
            // 当前工单的分任务的环节和状态被更新了，所以要更新到数据库
            updateSubTasksWithoutAttachment(info.getSubTasks());

            // 处理当前工单的流程，使其流转到“子流程-分任务办理子流程”环节
            ResultInfo resultInfo = this.uniFlowService.completeTask(taskId, WorkApplyOperationUtils.convertUserInfo(user),
                    DecisionObject.DECISION_JUMP, "跳转-分派",
                    opinion, WorkStatusEnum.SUB_CREATE_SUB_FLOW.getValue(),
                    WorkApplyOperationUtils.AUTO_NODE_HANDLERS,
                    info.isDispatched() ? WorkApplyOperationUtils.CREATE_SUB_FLOW_EXTENDS_DATA
                            : WorkApplyOperationUtils.NO_SUB_FLOW_EXTENDS_DATA, null);
            checkUniflowMethodInvokeResult(resultInfo, "组织人员办理提交后创建分任务子流程失败：");
            workDoneStatusInfoService.dealWorkApplyDoneStatus(info, null, createSubRes.getRight(), user);
        } catch (Exception e) {
            flowRollbackBack(user, createSubRes);
            throw new WorkApplyException("组织人员办理后创建分任务子流程失败，taskId:" + taskId, e);
        }
    }

    /**
     * 回滚ucc完成的任务.
     * @param user 当前用户.
     * @param createSubRes 请求结果.
     */
    private void flowRollbackBack(final LoginUserPojo user, final Pair<List<TaskObject>, List<WorkApplyInfo>> createSubRes) {
        if (null != createSubRes) {
            // 回滚ucc完成的任务
            WorkApplyOperationUtils.endSubFlow(uniFlowService, createSubRes.getLeft(), user);
        }
    }

    /**
     * 更新结果附件.
     *
     * @param info 工单信息
     */
    private void updateResultAttachment(final WorkApplyInfo info) {
        if (CollectionUtils.isNotEmpty(info.getReportAttachments())) {
            for (WorkApplyInfoAttachment resAttachment : info.getReportAttachments()) {
                //更新附件
                if (StringUtils.isBlank(resAttachment.getId())) {
                    resAttachment.setId(UUID.randomUUID().toString());
                    resAttachment.setWorkApplyId(info.getId());
                    // this.workApplyInfoMapper.deleteAttachmentByWorkApplyId(info.getId(), AttachTypeEnum.RESULT);
                    this.workApplyInfoMapper.insertWorkApplyInfoAttachment(resAttachment);
                }
            }
        }
    }

    /**
     * 责任人确认环节确认提交.
     *  @param taskId  任务id
     * @param info    工单信息
     * @param opinion 意见
     * @param user    操作用户
     * @param request
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void responderApprove(String taskId, WorkApplyInfo info, String opinion, LoginUserPojo user, HttpServletRequest request) {
        List<TaskObject> tasks = Lists.newArrayList();
        try {
            Preconditions.checkArgument(StringUtils.isNotBlank(taskId), TASK_ID_BLANK_ERROR_MSG);
            Preconditions.checkArgument(WorkApplyOperationUtils
                            .lockWorkApplyInfo(redisCacheUtil, info.getId(), retryCount, 0, retryWaitTime),
                    GET_LOCK_FAILED);
            //处理结果附件(上报附件)
            workSubTaskInternalService.handleReportAttachments(info.getSubTasks(), user, request);

            doConfirmSubmitBusiness(taskId, info, opinion, user, tasks, null);
        } catch (Exception e) {
            // 回滚ucc完成的任务
            WorkApplyOperationUtils.rollbackTasks(uniFlowService, tasks);
            throw new WorkApplyException("责任人、子流程责任部门人员确认环节确认提交失败，taskId:" + taskId, e);
        } finally {
            if (info != null) {
                redisCacheUtil.hdel(info.getId());
            }
        }
    }

    /**
     * 责任人确认、组织人员办理环节确认提交.
     *  @param taskId     任务id
     * @param info       工单信息
     * @param subApplyId 子工单id
     * @param opinion    意见
     * @param user       操作用户
     * @param request
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void confirmSimpleTask(final String taskId, final WorkApplyInfo info,
                                  final String subApplyId, final String opinion, final LoginUserPojo user, HttpServletRequest request) {
        List<TaskObject> tasks = Lists.newArrayList();
        try {
            Preconditions.checkArgument(StringUtils.isNotBlank(taskId), TASK_ID_BLANK_ERROR_MSG);
            Preconditions.checkArgument(StringUtils.isNotBlank(subApplyId), "子工单id不能为空！");
            Preconditions.checkNotNull(info, APPLY_NOT_EXISTS_ERROR_MSG);
            //处理结果附件(上报附件)，此处不应该有上报附件的操作，暂时注释，等确认业务处理后再删除
            // workSubTaskInternalService.handleReportAttachments(info, user, request);
            doConfirmSubmitBusiness(taskId, info, opinion, user, tasks, subApplyId);
        } catch (Exception e) {
            // 回滚ucc完成的任务
            WorkApplyOperationUtils.rollbackTasks(uniFlowService, tasks);
            throw new WorkApplyException("责任人、子流程责任部门人员确认环节单个子工单确认提交失败，taskId:" + taskId, e);
        } finally {
            if (info != null) {
                redisCacheUtil.hdel(info.getId());
            }
        }
    }

    @Override
    public List<WorkApplyInfo> queryDoneTask(final WorkApplyQueryVO vo) {
        PageHelper.startPage(vo.getPage(), vo.getPageSize());
        final List<WorkApplyInfo> workApplyInfos = workApplyInfoMapper.queryDoneTask(vo).stream()
                .filter(Objects::nonNull).collect(Collectors.toList());
        this.processWorkApplyInfoForWorkCenter(workApplyInfos);
        return workApplyInfos;
    }

    @Override
    public void updateNotifyMethod(final String id, final boolean notifyMethod) {
        workApplyInfoMapper.updateNotifyMethod(id, notifyMethod);
    }

    /**
     * 查询指定用户下所有待办子任务.
     *
     * @param handlerId 用户id
     * @return 该用户名下智办工单集合
     */
    @Override
    public List<WorkSubTaskInfo> findWorkSubTasksByUserId(String handlerId) {
        final Date todayZeroTime = DateUtils.truncate(new Date(), Calendar.DATE);
        return workApplyInfoMapper.findWorkSubTasksByUserId(handlerId, todayZeroTime);
    }

    /**
     * 查询指定id的所有分任务.
     *
     * @param idList 分任务id列表
     * @return 所有满足条件的分任务列表
     */
    @Override
    public List<WorkSubTaskInfo> findWorkSubTasksByIds(final List<String> idList) {
        return workApplyInfoMapper.getWorkSubTaskInfoByIds(idList);
    }

    @Override
    public WorkApplyInfoAttachment findWorkAttachmentById(String attachmentId) {
       return workSubTaskInternalService.findWorkAttachmentById(attachmentId);
    }

    public List<WorkApplyInfoAttachment> findWorkApplyInfoAttachmentsByApplyIds(final List<String> workApplyIds, final AttachTypeEnum type) {
        return workApplyInfoMapper.findWorkApplyInfoAttachmentsByApplyIds(workApplyIds, type);
    }

    @Override
    public List<List<PartyUserVo>> getResolveReceiver(final List<PartyOrgUserSelectItem> orgUserSelectItems) {
        return orgUserSelectItems.stream().map(item -> {
            if (SELECT_TYPE_ORG.equals(item.getType())) {
               return partyUserMapper.getGanshiByOrgUuid(item.getId()).stream()
                    .map(partyUser ->
                             new PartyUserVo(partyUser.getOaId(), partyUser.getDisplayName(), partyUser.getOrgShortName(), true))
                    .collect(Collectors.toList());
            } else {
                return Lists.newArrayList(new PartyUserVo(item.getId(), item.getLabel(), item.getOrgShortName()));
            }
        }).collect(Collectors.toList());
    }

    @Override
    public String checkReceiver(final List<PartyOrgUserSelectItem> orgUserSelectItems, final List<List<PartyUserVo>> users) {
        final Set<String> selectUids = new HashSet<>();
        final StringBuilder msg = new StringBuilder();
        //uid和党组织对应关系，用于生成验证信息
        final Map<String, String> uidAndOrgName = new HashMap<>();
        // uid和名称
        final Map<String, String> uidAndName = new HashMap<>();
        for (final ListIterator selectIt = orgUserSelectItems.listIterator(),
            userIt = users.listIterator(); selectIt.hasNext();) {
            final PartyOrgUserSelectItem partyOrgUserSelectItem = (PartyOrgUserSelectItem) selectIt.next();
            final Set<String> partyUserIds = ((List<PartyUserVo>) userIt.next()).stream()
                .peek(item -> {
                    if (StringUtils.isBlank(item.getUid())) {
                        msg.append(item.getOrgShortName() + "的党员" + item.getName() + "员工帐号不存在，请先联系省公司党办完成该配置");
                    } else {
                        uidAndName.put(item.getUid(), item.getName());
                    }

                })
                .map(PartyUserVo::getUid).collect(
                Collectors.toSet());
            if (SELECT_TYPE_ORG.equals(partyOrgUserSelectItem.getType())) {
                partyUserIds.stream().forEach(item -> uidAndOrgName.put(item, partyOrgUserSelectItem.getLabel()));
            }
            if (CollectionUtils.isEmpty(partyUserIds)) {
                // 党组织 找不到干事
                msg.append("无法分派任务：" + partyOrgUserSelectItem.getLabel() + "的干事未配置，请先联系省公司党办完成该配置");
            } else {
                final Set<String> intersectionUid = SetUtils.intersection(selectUids, partyUserIds);
                // 有交集说明 重复了
                if (CollectionUtils.isNotEmpty(intersectionUid)) {
                    //重复可能性有很多种，先选人再选党组织或者先选党组织再选人
                    final String names = intersectionUid.stream()
                            .map(uidAndName::get)
                            .collect(Collectors.joining(","));
                    if (SELECT_TYPE_ORG.equals(partyOrgUserSelectItem.getType())) {
                        msg.append(partyOrgUserSelectItem.getLabel() + "的干事包含了" + names + "，请勿重复指定分派对象。");
                    } else {
                        // 之前选的党组织里已经存在党员, 肯定只有一个的
                        final String partyOrgName = intersectionUid.stream()
                                .findAny()
                                .map(uidAndOrgName::get)
                                .orElse(null);
                        msg.append(partyOrgName + "的干事包含了" + names + "，请勿重复指定分派对象。");
                    }
                }
                selectUids.addAll(partyUserIds);
            }

        }
        return msg.toString();
    }

    /**
     * 办结确认。
     *
     * @param workApplyInfo 要办结确认的工单对象
     * @param opinion       处理意见
     * @param user          当前处理人
     */
    @Override
    @Transactional
    public void finishConfirm(final WorkApplyInfo workApplyInfo, final String opinion, final LoginUserPojo user) {
        // 先插入处理记录（此时状态status还没更新）
        workApplyInfoMapper.insertWorkApproveOpinion(WorkApplyOperationUtils
                .buildApproveOpinionNew(opinion, user, workApplyInfo, "办结", workApplyInfo.getStatus().getValue()));

        // 更新工单信息
        workApplyInfo.setStatus(WorkStatusEnum.END);
        workApplyInfo.setUpdateTime(new Date());
        workApplyInfo.setHandlerUid(null);
        workApplyInfo.setHandlerName(null);
        workApplyInfoMapper.updateWorkApplyInfo(workApplyInfo);
        UserInfo currentUserInfo = new UserInfo(user.getUserID(), user.getUserName());

        // 处理流程
        ResultInfo processResultInfo = uniFlowService.completeTask(workApplyInfo.getTiid(), currentUserInfo, "SUBMIT", "办结", opinion);
        if (!processResultInfo.isResult()) {
            throw new WorkApplyException(processResultInfo.getInfo());
        }
        // 办结只会影响当前工单
        workDoneStatusInfoService.dealWorkApplyDoneStatus(workApplyInfo, null, null, user);
    }

    /**
     * 执行责任人提交/组织人员确认提交操作.
     *
     * @param taskId     任务id
     * @param info       工单id
     * @param opinion    意见
     * @param user       操作人员
     * @param tasks      任务集合
     * @param subApplyId 子工单id
     * @return 是否全部完成
     */
    private boolean doConfirmSubmitBusiness(final String taskId, final WorkApplyInfo info, final String opinion,
                                            final LoginUserPojo user, final List<TaskObject> tasks,
                                            final String subApplyId) {
        boolean mainFlow = StringUtils.isBlank(info.getParentId());
        final WorkStatusEnum curState = mainFlow ? WorkStatusEnum.RESPONDER_APPROVE : WorkStatusEnum.SUB_ORG_USER_CONFIRM;
        final WorkStatusEnum nextState = mainFlow ? WorkStatusEnum.END : WorkStatusEnum.SUB_CREATOR_CONFIRM;
        workSubTaskInternalService.checkDataBeforeApprove(info, curState, user);
        final List<WorkApplyInfo> subApplys = this.workApplyInfoMapper.findWorkApplyInfoByParentApplyId(info.getId());
        Preconditions.checkArgument(CollectionUtils.isNotEmpty(subApplys), "当前工单的子工单为空！");
        final long finishedCount = subApplys.stream()
                .filter(it -> it.getStatus() == WorkStatusEnum.END).count();
        List<WorkApplyInfo> needFinishSubApplyList = subApplys.stream()
                .filter(it -> it.getStatus() == WorkStatusEnum.SUB_CREATOR_CONFIRM)
                .collect(Collectors.toList());
        Preconditions.checkArgument(CollectionUtils.isNotEmpty(needFinishSubApplyList),
                "待结束的子工单不能为空！id:" + info.getId());
        WorkApplyInfo subApply = null;
        if (StringUtils.isNotBlank(subApplyId)) {
            subApply = this.workApplyInfoMapper.findWorkApplyInfoById(subApplyId);
            Preconditions.checkNotNull(subApply, "待确认完成的子工单信息为空！");
        }
        int count = subApplys.size();
        // 在本次操作完成之后，是否所有子工单都完成了
        final boolean allFinish = subApply == null ?
                (needFinishSubApplyList.size() + finishedCount) == count
                : finishedCount + 1 == count;

        // 更新当前工单
        if (allFinish) {
            info.setStatus(nextState);
        }
        initApplyHandler(info, allFinish);
        info.setUpdateTime(DateTime.now().toDate());
        this.workApplyInfoMapper.updateWorkApplyInfo(info);

        //更新结果附件
        //updateResultAttachment(info);
        // 改成生成合并附件，如果没有合并附件，则生成空的合并目标附件
        List<WorkSubTaskInfo> workApplySubTasks = info.getSubTasks();
        if(CollectionUtils.isNotEmpty(workApplySubTasks)) {
            for(WorkSubTaskInfo workSubTaskInfo : workApplySubTasks) {
                if(CollectionUtils.isEmpty(workSubTaskInfo.getMergeAttachments())){
                    for (WorkApplyInfoAttachment templateAttachment : workSubTaskInfo.getTemplateAttachments()) {
                        templateAttachment.setId(UUID.randomUUID().toString());
                        templateAttachment.setWorkApplyId(workSubTaskInfo.getWorkApplyId());
                        templateAttachment.setType(AttachTypeEnum.MERGE);
                        List<WorkApplyInfoAttachment> subTaskMergeAttaches = Lists.newArrayList();
                        subTaskMergeAttaches.add(templateAttachment);
                        workSubTaskInfo.setMergeAttachments(subTaskMergeAttaches);
                        workApplyInfoMapper.insertWorkApplyInfoAttachment(templateAttachment);
                    }
                }
            }
            List<WorkApplyInfoAttachment> mergeAttachs = workApplyInfoMapper
                    .findWorkApplyInfoAttachmentsByApplyId(info.getId(), AttachTypeEnum.MERGE);
            info.setMergeAttachments(mergeAttachs);
        }

        //合并excel附件内容
        List<WorkApplyInfo> needDealSubApplyList = subApply == null ? needFinishSubApplyList
                : Lists.newArrayList(subApply);
        attachmentService.mergeResultAttachmentContentForWorkApply(info, needDealSubApplyList);

        // 更新分任务信息（包含当前工单的和下级工单的）
        workSubTaskInternalService.confirmSubTasks(info, needDealSubApplyList, allFinish);

        // 处理记录无论如何都要插入
        workApplyInfoMapper.insertWorkApproveOpinion(WorkApplyOperationUtils.buildApproveOpinion(opinion, user, info, "提交"));

        // 更新子工单状态和任务信息
        List<TaskObject> taskObjects = WorkApplyOperationUtils.finishConfirmSubTask(workApplyInfoMapper, uniFlowService,
                needDealSubApplyList, opinion, user);
        if (CollectionUtils.isNotEmpty(taskObjects)) {
            CollectionUtils.addAll(tasks, taskObjects.iterator());
        }

        // 完成当前任务
        if (allFinish) {
            TaskObject[] curTasks = WorkApplyOperationUtils.finishConfirmTask(uniFlowService, info, taskId,
                    opinion, user, allFinish);
            if (ArrayUtils.isNotEmpty(curTasks)) {
                CollectionUtils.addAll(tasks, curTasks);
            }
        }
        return allFinish;
    }

    /**
     * 设置工单的处理人.
     *
     * @param info      工单信息
     * @param allFinish 是否全部完成
     */
    private void initApplyHandler(final WorkApplyInfo info, final boolean allFinish) {
        if (StringUtils.isBlank(info.getParentId())) {
            // 如果是主流程，确认后则下一处理人为空
            info.setHandlerUid(null);
            info.setHandlerName(null);
        } else {
            // 如果是子流程，则需要判断是否全部完成
            if (allFinish) {
                info.setHandlerUid(info.getCreatorUid());
                info.setHandlerName(info.getCreatorName());
            } else {
                info.setHandlerUid(null);
                info.setHandlerName(null);
            }
        }
    }

    /**
     * 分任务子流程组织人员确认环节确认.
     *  @param info    工单信息
     * @param taskId  任务id
     * @param user    操作用户
     * @param opinion 意见
     * @param request
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void subOrgUserConfirm(WorkApplyInfo info, String taskId, LoginUserPojo user, String opinion, HttpServletRequest request) {
        List<TaskObject> tasks = Lists.newArrayList();
        try {
            Preconditions.checkNotNull(info, APPLY_NOT_EXISTS_ERROR_MSG);
            Preconditions.checkArgument(WorkApplyOperationUtils
                            .lockWorkApplyInfo(redisCacheUtil, info.getParentId(), retryCount, 0, retryWaitTime),
                    GET_LOCK_FAILED);
            //处理结果附件
            workSubTaskInternalService.handleReportAttachments(info.getSubTasks(), user, request);
            //更新附件
            updateResultAttachment(info);
            Preconditions.checkArgument(StringUtils.isNotBlank(taskId), TASK_ID_BLANK_ERROR_MSG);
            boolean allFinish = doConfirmSubmitBusiness(taskId, info, opinion, user, tasks, null);
            // 父任务需要提交到责任人确认/组织人员确认
            if (allFinish) {
                workSubTaskInternalService.submitParentApplyWhenSubApplySubmit(info, user, opinion);
            }
        } catch (Exception e) {
            throw new WorkApplyException("子流程责任部门人员确认环节确认提交失败，taskId:" + taskId, e);
        } finally {
            if (info != null) {
                this.redisCacheUtil.hdel(info.getParentId());
            }
        }
    }

    /**
     * 查询参数对象.
     *
     * @param vo 查询参数对象
     * @return 查询结果
     */
    @Override
    public List<WorkApplyInfo> queryWorkApply(final WorkApplyQueryVO vo) {
        PageMethod.startPage(vo.getPage(), vo.getPageSize());
        return workApplyInfoMapper.search(vo);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void terminateApply(final String workApplyId, final LoginUserPojo user) {
        //验证权限及状态
        //验证权限及状态
        final WorkApplyInfo workApplyInfo = getAndCheckPermission(workApplyId, user);
        Preconditions.checkArgument(workApplyInfo.getStatus() != WorkStatusEnum.END, "已经办结的工单不用结报");

        final Date now = new Date();
        //处理工单，返回未办结的子工单
        List<WorkApplyInfo> subApplys = doTerminateByApply(workApplyInfo, now, user);

        // 工单可以无限层层向下分派，递归处理
        while (CollectionUtils.isNotEmpty(subApplys)) {
            subApplys = subApplys.stream()
                    .map(info -> doTerminateByApply(info, now, user))
                    .flatMap(List::stream)
                    .collect(Collectors.toList());
        }

    }

    @Override
    public void deleteDraft(final String workApplyId, final LoginUserPojo user) {
        //验证权限及状态
        final WorkApplyInfo workApplyInfo = getAndCheckPermission(workApplyId, user);
        workApplyInfoMapper.deleteDraft(workApplyId);
        String piid = workApplyInfo.getPiid();
        // 用章申请没存piid, 需要再查一一次
        if (StringUtils.equals(workApplyInfo.getFlowType(), BizConstant.CHAPTER_APPLY_PROCESS_CODE)
            && Objects.nonNull(workApplyInfo.getChapterDate())) {
            final ProcessQueryParam param = new ProcessQueryParam();
            param.setApplyId(workApplyInfo.getId());
            final ResultInfo resultInfo = uniFlowService.queryProcessInstances(param);
            piid = Optional.ofNullable((ProcessObject[]) resultInfo.getData())
                    .filter(ArrayUtils::isNotEmpty)
                    .map(item -> item[0])
                    .map(ProcessObject::getPiid)
                    .orElse(null);
        }
        // 停止流程
        if (StringUtils.isNotBlank(piid)) {
            uniFlowService.stopProcess(workApplyInfo.getPiid(), new UserInfo(user.getUserID(), user.getUserName()), "删除");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateReadType(String workApplyId, LoginUserPojo user) {
        workApplyInfoMapper.updateReadType(workApplyId, user.getUserID());

        // 完成阅办流程
        final WorkApplyInfo workApplyInfo = workApplyInfoMapper.findWorkApplyInfoById(workApplyId);
        final ReadQueryParam readQueryParam = new ReadQueryParam();
        readQueryParam.setApplyId(workApplyInfo.getApplyId());
        readQueryParam.setOwner(user.getUserID());
        readQueryParam.setPiid(workApplyInfo.getPiid());
        ResultInfo resultInfo = uniFlowService.queryReads(readQueryParam);
        final ReadObject[] readObjects = (ReadObject[]) resultInfo.getData();
        // 获取流程返回的信息，需要用下标0获取当前阅读项状态，readObjects[0].getState()获取阅读项状态
        if (ArrayUtils.isNotEmpty(readObjects) && readObjects.length == 1 && readObjects[0].getState() == 1) {
            uniFlowService.completeRead(readObjects[0].getRiid(), null);
        }


    }

    private WorkApplyInfo getAndCheckPermission(String workApplyId, LoginUserPojo user) {
        final WorkApplyInfo workApplyInfo = workApplyInfoMapper.findWorkApplyInfoById(workApplyId);
        Preconditions.checkNotNull(workApplyInfo, APPLY_NOT_EXISTS_ERROR_MSG);
        Preconditions.checkArgument(WorkApplyOperationUtils.isMainApply(workApplyInfo), "只能对主工单进行操作");
        Preconditions.checkArgument(StringUtils.equals(workApplyInfo.getCreatorUid(), user.getUserID()), "对不起，您没有没有操作权限");
        return workApplyInfo;
    }

    /**
     * 结报当前工单.
     *
     * @param workApplyInfo 工单信息.
     * @param now           当前时间
     * @param user
     * @return 未办结的子工单列表
     */
    private List<WorkApplyInfo> doTerminateByApply(final WorkApplyInfo workApplyInfo, final Date now, LoginUserPojo user) {
        final String id = workApplyInfo.getId();
        LOGGER.info("doTerminateByApply id:{}", id);
        // 查询所有待办: 可能存在工单没有待办的情况
        TaskQueryParam queryParam = new TaskQueryParam();
        queryParam.setStates(new int[] {TaskObject.STATE_READY});
        queryParam.setApplyId(WorkApplyOperationUtils.getUniflowApplyId(workApplyInfo));
        ResultInfo resultInfo = uniFlowService.queryTasksByPage(queryParam, 1, BizConfig.MAX_PAGE_SIZE);
        checkUniflowMethodInvokeResult(resultInfo, "结报查询流程平台待办失败：");

        // 过滤待办创建已办记录
        Optional.ofNullable((TaskPageObject) resultInfo.getData())
                        .map(TaskPageObject::getTaskObjects)
                        .ifPresent(taskObjects ->
                                Arrays.stream(taskObjects)
                                .map(TaskObject::getOwner)
                                .filter(owner -> !StringUtils.equals(owner.getUserId(), WorkApplyOperationUtils.AUTO_NODE_HANDLER.getUserId()))
                                .forEach(owner -> {
                                    // 构建已办记录
                                    WorkDoneStatusInfo info = new WorkDoneStatusInfo();
                                    info.setId(UUID.randomUUID().toString());
                                    info.setWorkApplyId(id);
                                    info.setDoneType(DoneTypeEnum.TERMINATE_TASK);
                                    info.setOwnerUid(owner.getUserId());
                                    info.setOwnerName(owner.getUserName());
                                    info.setCreateTime(now);
                                    info.setUpdateTime(now);
                                    workDoneStatusInfoService.saveDoneInfo(info);
                                })
                        );

        workApplyInfoMapper.updateApplyStatusToTerminate(id, now);

        // 停止流程
        uniFlowService.stopProcess(workApplyInfo.getPiid(), new UserInfo(user.getUserID(), user.getUserName()), "报结");

        // 返回子工单
        return workApplyInfoMapper.findWorkApplyInfoByParentApplyId(id)
                .stream()
                .filter(apply -> ObjectUtils.notEqual(apply.getStatus(), WorkStatusEnum.END))
                .collect(Collectors.toList());
    }

    /**
     * 邮件发送
     */
    private void sendEmailFunction(Map<String, Object> map) {
        String title = "党建机器人-任务派发流程：{0}";
        final String contentTemplate = "<a href=\"{0}\">请处理党建机器人-任务派发流程任务工单：{1}</a>"
                + "<br/>发起人：{2}"
                + "<br/>发起时间：{3}";
        emailService.sendEmailFunction(title, contentTemplate, BizConstant.WISDOM_MAIN_PROCESS_CODE, talkConfig.getSiteUrl(), map);
    }

    /**
     * 加工处理工单信息，用于显示.
     * 修改流程发起人和发起时间，更新成主工单发起人和发起时间
     * @param workApplyInfos 工单信息，会修改对象
     */
    private void processWorkApplyInfoForWorkCenter(final List<WorkApplyInfo> workApplyInfos) {

        // 工单ID和主工单map，用于取拟搞时间
        final Map<String, WorkApplyInfo> applyIdMainApplyInfoMap = uniFlowService.getApplyIdMainApplyMap(workApplyInfos);

        workApplyInfos.forEach(item ->

            // 设置发起人和发起时间
            Optional.ofNullable(applyIdMainApplyInfoMap.get(item.getApplyId()))
                    .ifPresent(apply -> {
                        item.setCreateTime(apply.getCreateTime());
                        item.setCreatorName(apply.getCreatorName());
                    })
        );

    }

    @Override
    public List<WorkApplyInfo> queryReadTask(final WorkApplyQueryVO vo) {
        PageHelper.startPage(vo.getPage(), vo.getPageSize());
        final List<WorkApplyInfo> workApplyInfos = workApplyInfoMapper.queryReadTask(vo);
        this.processWorkApplyInfoForWorkCenter(workApplyInfos);
        return workApplyInfos;
    }

    /**
     * 邮件发送
     */
    private void sendEmailFunctionByCopySubmit(final Map<String, Object> map) {
        final String title = "党建机器人-任务抄送";
        final String contentTemplate = "<a href=\"{0}\">党建机器人-任务抄送：{1}</a>"
                + "<br/>发起人：{2}"
                + "<br/>发起时间：{3}";
        emailService.sendEmailFunction(title, contentTemplate, null, talkConfig.getSiteUrl(), map);
    }

    /**
     * 上传WPS共享编辑附件
     * @param workApplyInfo 主工单信息对象
     */
    @Override
    public void uploadSharedEditingAttachment(final WorkApplyInfo workApplyInfo) {
        Optional.ofNullable(workApplyInfo)
                .map(WorkApplyInfo::getSubTasks) // 从主任务工单中获取子任务信息
                .map(Collection::stream)
                .orElseGet(Stream::empty)
                .filter(WorkSubTaskInfo::isApplySharedEditing) // 只操作开启共享编辑的数据
                .map(WorkSubTaskInfo::getTemplateAttachments) // 从子任务信息列表里面获取模板信息的任务附件信息
                .map(ListUtils::emptyIfNull)
                .flatMap(Collection::stream)
                .filter(Objects::nonNull)
                .forEach(oaDocsSdkService::uploadFile); // 上传文件
    }

    /**
     * 转交工单.
     * @param taskId 任务id
     * @param info 工单信息
     * @param opinion 意见
     * @param user 当前用户
     * @param transferUser 转交人
     * @throws WorkApplyException 智办工单处理异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transfer(final String taskId, final WorkApplyInfo info, final String opinion, final LoginUserPojo user, final SelectUser transferUser) throws WorkApplyException {
        try {
            // 验证任务数据是否符合处理标准
            Preconditions.checkArgument(StringUtils.isNotBlank(taskId), TASK_ID_BLANK_ERROR_MSG);
            Preconditions.checkArgument(StringUtils.isNotBlank(info.getHandlerUid()) && StringUtils.equals(user.getUserID(), info.getHandlerUid()), "当前用户不是工单处理人！");
            workSubTaskInternalService.checkDataBeforeApprove(info, null, user);
            // 设置工单数据
            info.setUpdateTime(LocalDateTime.now().toDate());
            info.setHandlerUid(transferUser.getUserId());
            info.setHandlerName(transferUser.getUserName());
            info.setAssignUserId(transferUser.getUserId());
            info.setAssignUserName(transferUser.getUserName());
            // 更新主工单数据
            this.workApplyInfoMapper.updateWorkApplyInfo(info);
            // 插入审批记录
            workApplyInfoMapper.insertWorkApproveOpinion(WorkApplyOperationUtils.buildApproveOpinionNew(opinion, user, info, ApproveConstant.DEFAULT_TRANSFER_NAME, info.getStatus().getValue()));
            // 当前工单的分任务的环节和状态被更新了，所以要更新到数据库
            updateSubTasksWithoutAttachment(info.getSubTasks());
            // 处理主工单
            final ResultInfo resultInfo = this.uniFlowService.transferTask(taskId, new UserInfo[]{new UserInfo(transferUser.getUserId(), transferUser.getUserName())}, ApproveConstant.DEFAULT_TRANSFER_NAME, opinion);
            // 验证ucc接口返回结果
            checkUniflowMethodInvokeResult(resultInfo, "任务转交失败：");
            // 操作主工单已办信息，不用操作父工单和子工单
            workDoneStatusInfoService.dealWorkApplyDoneStatus(info, null, null, user);
            // 获取父工单信息
            final WorkApplyInfo parentWorkApplyInfo = Optional.ofNullable(info.getParentId())
                    .filter(StringUtils::isNotBlank)
                    .map(workApplyInfoMapper::findWorkApplyInfoById)
                    .orElse(null);
            // 如果有父工单，需要更新父工单信息
            if (Objects.nonNull(parentWorkApplyInfo)) {
                // 更新工单主送对象信息
                transferUpdateWorkApplyInfo(parentWorkApplyInfo, user, transferUser);
                // 更新状态表的当前处理人信息
                transferUpdateWorkStatusInfo(parentWorkApplyInfo, user, transferUser);
                // 插入父工单审批记录
                workApplyInfoMapper.insertWorkApproveOpinion(WorkApplyOperationUtils.buildApproveOpinionNew(opinion, user, parentWorkApplyInfo, String.format("转交（%s）", info.getTitle()), info.getStatus().getValue()));
            }
        } catch (final IllegalArgumentException | NullPointerException | JsonProcessingException e) {
            final String message = String.format("转交工单失败，工单信息[%s]，当前用户[%s]", info, user);
            LOGGER.error(message, e);
            throw new WorkApplyException(message, e);
        }
    }

    /**
     * 转交工单 更新状态表的当前处理人信息
     * @param parentWorkApplyInfo 父工单数据信息
     * @param currentUser 当前登录用户
     * @param transferUser 转交用户
     */
    private void transferUpdateWorkStatusInfo(final WorkApplyInfo parentWorkApplyInfo, final LoginUserPojo currentUser, final SelectUser transferUser) {
        final String currentUserId = currentUser.getUserID(); // 当前用户id
        final String currentUserName = currentUser.getUserName(); // 当前用户名称
        final String transferUserId = transferUser.getUserId(); // 转交人id
        final String transferUserName = transferUser.getUserName(); // 转交人名称
        // 需要更新状态表的当前处理人信息
        final String currentHandlerUids = Arrays.stream(StringUtils.split(parentWorkApplyInfo.getCurrentHandlerUid(), Constants.STRING_SEMICOLON))
                .map(u -> StringUtils.equals(u, currentUserId) ? transferUserId : u)
                .collect(Collectors.joining(Constants.STRING_SEMICOLON));
        final String currentHandlerNames = Arrays.stream(StringUtils.split(parentWorkApplyInfo.getCurrentHandlerName(), Constants.STRING_SEMICOLON))
                .map(u -> StringUtils.equals(u, currentUserName) ? transferUserName : u)
                .collect(Collectors.joining(Constants.STRING_SEMICOLON));
        final WorkStatusInfo workStatusInfo = workDoneStatusInfoMapper.getWorkStatusInfoByWorkApplyId(parentWorkApplyInfo.getId());
        workStatusInfo.setCurrentHandlerUid(currentHandlerUids);
        workStatusInfo.setCurrentHandlerName(currentHandlerNames);
        workStatusInfo.setUpdateTime(DateTime.now().toDate());
        // 更新状态信息
        workDoneStatusInfoMapper.updateWorkStatusInfo(workStatusInfo);
    }

    /**
     * 转交工单 更新工单主送对象信息
     * @param parentWorkApplyInfo 父工单数据信息
     * @param currentUser 当前登录用户
     * @param transferUser 转交用户
     * @throws JsonProcessingException json数据处理异常
     */
    private void transferUpdateWorkApplyInfo(final WorkApplyInfo parentWorkApplyInfo, final LoginUserPojo currentUser, final SelectUser transferUser)
            throws JsonProcessingException {
        // 当前登录人id
        final String currentUserId = currentUser.getUserID();
        // 构建主送对象uid信息集合
        final List<ReceiverUidVo> receiverUids = transferCreateReceiverUids(parentWorkApplyInfo.getReceiverUid(), currentUserId, transferUser);
        // 构建主送对象name信息集合
        final List<List<ReceiverNameVo>> receiverNames = transferCreateReceiverNames(parentWorkApplyInfo.getReceiverName(), currentUserId, transferUser);
        // 保存更新后的主送对象信息
        parentWorkApplyInfo.setReceiverUid(JSON.toJSON(receiverUids).toString());
        parentWorkApplyInfo.setReceiverName(JSON.toJSON(receiverNames).toString());
        parentWorkApplyInfo.setUpdateTime(DateTime.now().toDate());
        workApplyInfoMapper.updateWorkApplyInfo(parentWorkApplyInfo);
    }

    /**
     * 转交工单 构建主送对象uid信息集合
     * @param receiverUid 主送人uid数据
     * @param currentUserId 当前登录人id
     * @param transferUser 转交人对象
     * @return 主送对象uid信息集合
     * @throws JsonProcessingException json数据处理异常
     */
    private List<ReceiverUidVo> transferCreateReceiverUids(final String receiverUid, final String currentUserId, final SelectUser transferUser)
            throws JsonProcessingException {
        /* receiverUid数据格式示例
            [
                {\"id\":\"xxx\",\"label\":\"某某某\",\"type\":\"person\",\"orgShortName\":\"某某某党支部\"},
                {\"id\":\"xxx\",\"label\":\"某某某某某某党支部\",\"type\":\"org\"}
            ]
         */
        final List<ReceiverUidVo> oldReceiverUids = objectMapper.readValue(receiverUid,
                objectMapper.getTypeFactory().constructCollectionType(List.class, ReceiverUidVo.class));
        // 主送对象uid未作改变开关对象默认为true，改变之后则不需要再判断是否为当前用户，后续数据不再做改变
        final AtomicBoolean noChangesMadeUid = new AtomicBoolean(true);
        // 将主送对象uid为当前登录人的数据变更为转交人数据，只需要变更一次，其他数据保持原状
        return oldReceiverUids.stream()
                .map(uidVo -> {
                    // 是否需要变更当前数据
                    if(transferGetNeedChange(noChangesMadeUid, uidVo.getId(), currentUserId)) {
                        // 如果状态是需要变更，则将主送人数据改为转交人数据，否则用主送人数据
                        return new ReceiverUidVo()
                                .setId(transferUser.getUserId())
                                .setLabel(transferUser.getUserName())
                                .setOrgShortName(transferUser.getOrgShortName())
                                .setType(transferUser.getType());
                    }
                    // 如果不需要变更，则直接返回原数据
                    return uidVo;
                }).collect(Collectors.toList());
    }

    /**
     * 转交工单 构建主送对象name信息集合
     * @param receiverName 主送人name数据
     * @param currentUserId 当前登录人id
     * @param transferUser 转交人对象
     * @return 主送对象name信息集合
     * @throws JsonProcessingException json数据处理异常
     */
    private List<List<ReceiverNameVo>> transferCreateReceiverNames(final String receiverName, final String currentUserId, final SelectUser transferUser)
            throws JsonProcessingException {
        /* receiverName数据格式示例
            [
                [
                    {\"uid\":\"xxx\",\"name\":\"某某某\",\"orgShortName\":\"某某某党支部\",\"queryByOrg\":false}
                ],[
                    {\"uid\":\"xxx\",\"name\":\"某某某\",\"orgShortName\":\"某某某某某某党支部\",\"queryByOrg\":true},
                    {\"uid\":\"xxx\",\"name\":\"某某某\",\"orgShortName\":\"某某某某某某党支部\",\"queryByOrg\":true}
                ]
            ]
         */
        final List<List<ReceiverNameVo>> oldReceiverNames = objectMapper.readValue(receiverName,
                objectMapper.getTypeFactory().constructCollectionType(List.class,
                        objectMapper.getTypeFactory().constructCollectionType(List.class, ReceiverNameVo.class)));
        // 主送对象name未作改变开关对象默认为true，改变之后则不需要再判断是否为当前用户，后续数据不再做改变
        final AtomicBoolean noChangesMadeName = new AtomicBoolean(true);
        // 将主送对象name为当前登录人的数据变更为转交人数据，只需要变更一次，其他数据保持原状
        return oldReceiverNames.stream()
                .map(nameVos -> nameVos.stream()
                        .map(nameVo -> {
                            // 是否需要变更当前数据
                            if(transferGetNeedChange(noChangesMadeName, nameVo.getUid(), currentUserId)) {
                                // 如果状态是需要变更，则将主送人数据改为转交人数据，否则用主送人数据
                                return new ReceiverNameVo()
                                        .setUid(transferUser.getUserId())
                                        .setName(transferUser.getUserName())
                                        .setOrgShortName(transferUser.getOrgShortName())
                                        .setQueryByOrg(nameVo.isQueryByOrg());
                            }
                            // 如果不需要变更，则直接返回原数据
                            return nameVo;
                        }).collect(Collectors.toList()))
                .collect(Collectors.toList());
    }

    /**
     * 转交工单 获取是否需要变更当前数据
     * @param noChangesMade 未作改变开关对象
     * @param receiverUserId 主送用户Id
     * @param currentUserId 当前用户id
     * @return 是否需要变更
     */
    private boolean transferGetNeedChange(final AtomicBoolean noChangesMade, final String receiverUserId, final String currentUserId) {
        // 未作改变，并且当前主送人是当前登录人，需要变更当前数据
        final boolean needChange = noChangesMade.get() && StringUtils.equals(receiverUserId, currentUserId);
        // 如果需要变更当前数据，将未作改变开关对象状态改为false
        if(needChange) {
            noChangesMade.set(false);
        }
        return needChange;
    }

}
